package com.pisolution.scheduler.runtime.registry;

import com.pisolution.scheduler.core.utils.OSUtils;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.Set;

/**
 * 节点心跳上传线程 心跳数据包由多段信息构成.
 *
 * <ul>
 *     <li>处理器利用率</li>
 *     <li>内存利用率</li>
 *     <li>处理器平均负载</li>
 *     <li>可用物理内存大小</li>
 *     <li>处理器最大平均负载</li>
 *     <li>主机预留内存占比</li>
 *     <li>节点注册时间</li>
 *     <li>心跳数据包创建时间</li>
 *     <li>主机健康状态</li>
 *     <li>节点进程号</li>
 * </ul>
 *
 * @author sora
 * @version 0.1.0
 */
public final class HeartbeatTask implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(HeartbeatTask.class);

    // -------------------------------------------------------------------------------------------------

    /** 示意当前主机处于健康状态. */
    private static final int NORMAL_STATUS = 0;

    /** 示意当前主机处于不健康状态. */
    private static final int ABNORMAL_STATUS = 1;

    // -------------------------------------------------------------------------------------------------

    /** 节点注册开始时间. */
    private final String startTime;

    /** 主机预留内存. */
    private final double reservedMemory;

    /** 主机处理器最大平均负载. */
    private final double maxCPULoadAvg;

    /** 心跳数据包存储路径. */
    private final Set<String> heartbeatPaths;

    /** 心跳数据包上传辅助工具. */
    private final RegistryCenter center;

    // -------------------------------------------------------------------------------------------------

    /** 临时性质的处理器各项指标记录容器 用于计算处理器利用率. */
    private long[] previousCPUTicks;

    // -------------------------------------------------------------------------------------------------
    //  初始化
    // -------------------------------------------------------------------------------------------------

    /**
     * 初始化心跳任务实例.
     *
     * @param startTime 节点注册开始时间.
     * @param reservedMemory 主机预留内存.
     * @param maxCPULoadAvg 主机处理器最大平均负载.
     * @param heartbeatPaths 心跳数据包上传路径.
     * @param center 注册中心.
     */
    public HeartbeatTask(
        final String startTime,
        final double reservedMemory,
        final double maxCPULoadAvg,
        final Set<String> heartbeatPaths,
        final RegistryCenter center
    ) {
        this.startTime = startTime;
        this.reservedMemory = reservedMemory;
        this.maxCPULoadAvg = maxCPULoadAvg;
        this.heartbeatPaths = heartbeatPaths;
        this.center = center;

        this.previousCPUTicks = OSUtils.cpuTicks();
    }

    // -------------------------------------------------------------------------------------------------
    //  心跳数据包上传
    // -------------------------------------------------------------------------------------------------

    @Override
    public void run() {
        try {
            // 1. 计算节点可用内存与处理器负载.
            final double availablePhysicalMemory = OSUtils.availablePhysicalMemory();
            final double loadAverage = OSUtils.loadAverage();

            // 2. 检查主机健康状态.
            int status = NORMAL_STATUS;

            if (availablePhysicalMemory < this.reservedMemory || loadAverage > this.maxCPULoadAvg) {
                LOGGER.warn("The available physical memory is too low or the cpu load is too high.");

                status = ABNORMAL_STATUS;
            }

            // 3. 构建心跳数据包.
            final StringBuilder builder = new StringBuilder(100);

            builder
                .append(OSUtils.cpuUsage(this.previousCPUTicks))
                .append(',')
                .append(OSUtils.memoryUsage())
                .append(',')
                .append(loadAverage)
                .append(',')
                .append(availablePhysicalMemory)
                .append(',')
                .append(this.maxCPULoadAvg)
                .append(',')
                .append(this.reservedMemory)
                .append(',')
                .append(this.startTime)
                .append(',')
                .append(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"))
                .append(',')
                .append(status)
                .append(',')
                .append(OSUtils.getProcessID());

            // 4. 记录当前处理器各项性能指标.
            this.previousCPUTicks = OSUtils.cpuTicks();

            // 5. 上传心跳数据包到指定路径.
            for (final String heartbeatPath: this.heartbeatPaths) {
                this.center.getClient().update(heartbeatPath, builder.toString());
            }
        } catch (Throwable t) {
            LOGGER.error("An error occurred while uploading the heartbeat information to the registry center.", t);
        }
    }
}
